package com.txtsqlclient.ui.helper;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author spadda
 */
public class TabularLayoutHelper {

	public static final String NEW_LINE = System.getProperty("line.separator");

	private static Log LOGGER = LogFactory.getLog(TabularLayoutHelper.class);

	protected static String getRowSeparator(final int width,
			final int numOfCols, final char rowSeparator) {
		final StringBuilder buff = new StringBuilder();
		// Add colWidth + 1 '-'s. The extra one is for the column separator '|',
		// put at the left of first column.
		for (int i = 0; i < width + numOfCols + 1; i++) {
			buff.append(rowSeparator);
		}
		buff.append(NEW_LINE);
		return buff.toString();
	}

	public static String print(final List<List<String>> data, final int maxWidth) {
		final int[] colWidths = getColumnWidths(data, maxWidth);

		int totalColWidth = 0;
		for (int colWidth : colWidths) {
			totalColWidth += colWidth;
		}
		final String rowSeparator = getRowSeparator(totalColWidth,
				colWidths.length, '-');
		final StringBuilder buff = new StringBuilder();
		buff.append(rowSeparator);

		for (List<String> row : data) {
			buff.append(getFormattedRow(row, colWidths, '|'));
			buff.append(rowSeparator);
		}
		return buff.toString();
	}

	/**
	 * This method is required to calculate the width to be allocated for each
	 * column for display purpose.
	 * 
	 * @param data
	 *            The 2-D matrix of cells containing the data to be displayed.
	 * @param maxWidth
	 *            The maximum width (for all columns combined).
	 * @param colWidths
	 *            The width to be allocated for each column.
	 * @return the total width to be used for display.
	 */
	public static int[] getColumnWidths(final List<List<String>> data,
			final int maxWidth) {
		final int numOfColumns = data.get(0).size();
		final int[] colWidths = new int[numOfColumns];
		// average of the widths of data present in each column (the average is
		// calculated by summing up the width of all cells in a particular
		// column).
		final float[] avgColWidths = new float[numOfColumns];
		// Total of the widths present in the above array.
		float avgColWidthsTotal = 0;
		// Total of the max widths of each column.
		int maxColWidthsTotal = 0;
		// Total assigned width.
		int totalAssignedWidth = 0;
		for (List<String> row : data) {
			int i = 0;
			for (String cell : row) {
				avgColWidths[i] += (cell != null) ? cell.length() : 0;
				colWidths[i] = (cell != null) ? (Math.max(cell.length(),
						colWidths[i])) : colWidths[i];
				i++;
			}
		}
		for (int i = 0; i < colWidths.length; i++) {
			avgColWidths[i] = avgColWidths[i] / data.size();
			avgColWidthsTotal += avgColWidths[i];
			maxColWidthsTotal += colWidths[i];
			LOGGER.debug("Maximum width of column-" + (i + 1) + " is "
					+ colWidths[i]);
		}
		// for (int i = 0; i < colWidths.length; i++) {
		// // Get all cells in i-th column
		// final List<String> column = data.get(i);
		// int total = 0;
		// int maxColWidth = 0;
		// for (final String cell : column) {
		// total += cell != null ? cell.length() : 0;
		// maxColWidth = cell != null ? (Math.max(cell.length(),
		// maxColWidth)) : maxColWidth;
		// }
		// maxColWidthsTotal += maxColWidth;
		// // Find average size of the column
		// final float avg = total / column.size();
		// avgColWidths[i] = avg;
		// avgColWidthsTotal += avg;
		// // Set maxWidth into column widths. This array will be modified
		// // later if the data overflows maxWidth.
		// colWidths[i] = maxColWidth;
		// LOGGER.debug("Maximum width of column-" + (i + 1) + " is "
		// + maxColWidth);
		// }
		LOGGER.debug("Maximum width of all columns included is "
				+ maxColWidthsTotal);
		if (maxColWidthsTotal <= maxWidth) {
			totalAssignedWidth = maxColWidthsTotal;
		} else {
			for (int i = 0; i < colWidths.length; i++) {
				colWidths[i] = (int) ((avgColWidths[i] * maxWidth) / avgColWidthsTotal);
				if (colWidths[i] == 0) {
					colWidths[i] = 1;
				}
				totalAssignedWidth += colWidths[i];
				LOGGER.debug("Assigned width of column-" + (i + 1) + " is "
						+ colWidths[i]);
			}
			LOGGER.debug("Total Assigned width of all columns is "
					+ totalAssignedWidth);
			// If the width has not been completely assigned, assign the extra
			// width
			// to some of the columns.
			if (totalAssignedWidth < maxWidth) {
				// An incrementing index
				int i = 0;
				while (maxWidth > totalAssignedWidth) {
					// Assign 1 unit to each column.
					// ASSUMPTION: The unassignedWidth will not be greater than
					// the
					// number of columns, because of the rounding.
					colWidths[i++] += 1;
					totalAssignedWidth += 1;
					LOGGER.debug("Added 1 pt to column " + i
							+ " as part of adjustment");
				}
			}
			// If more width than available has been assigned, remove the extra
			// width from some of the columns.
			else if (totalAssignedWidth > maxWidth) {
				// An incrementing index
				int i = 0;
				while (totalAssignedWidth > maxWidth) {
					// Remove 1 unit from each column, if the column's width is
					// more
					// than 1.
					if (colWidths[i] > 1) {
						colWidths[i++] -= 1;
						totalAssignedWidth -= 1;
						LOGGER.debug("Removed 1 pt from column " + i
								+ " as part of adjustment");
					}
				}
			}
		}
		return colWidths;
	}

	public static String getFormattedRow(final List<String> row,
			final int[] colWidths, final char colSeparator) {
		final StringBuilder buff = new StringBuilder();
		final boolean[] done = new boolean[colWidths.length];
		int lineNum = 0;
		outer: while (true) {
			int i = 0;
			for (final String cell : row) {
				// Add a Column Separator for each line, only before the
				// beginning of the first column.
				if (i == 0) {
					buff.append(colSeparator);
				}
				final int width = colWidths[i];
				if (done[i]) {
					// If there are no more chars to be printed for this column,
					// print
					// 'width' number of empty spaces.

					// The formatting string, "%1$-{width}s" specifies that the
					// argument should be printed 'left-justified' using 'width'
					// no. of chars (Refer to:
					// http://java.sun.com/j2se/1.5.0/docs/api/java/util/Formatter.html#syntax)
					final String s = String.format("%1$-" + width + "s", "");
					buff.append(s);
				} else {
					// If there are any more chars to be printed for this
					// column.
					final int numOfCharsPrinted = lineNum * width;
					// If this is the last line on which any chars from this
					// column can
					// be printed. Pad the rest of the chars with empty spaces.
					if (cell == null) {
						final String s = String
								.format("%1$-" + width + "s", "");
						buff.append(s);
						done[i] = true;
					} else if (numOfCharsPrinted + width >= cell.length()) {
						final String s = String.format("%1$-" + width + "s",
								cell.substring(numOfCharsPrinted));
						buff.append(s);
						done[i] = true;
					} else {
						// There are still more chars to be printed from this
						// column.
						buff.append(cell.substring(numOfCharsPrinted,
								numOfCharsPrinted + width));
						done[i] = false;
					}
				}
				buff.append(colSeparator);
				i++;
			}
			for (boolean d : done) {
				// If at least one of the columns is not done, continue printing
				// to the next
				// line.
				if (!d) {
					buff.append(NEW_LINE);
					lineNum++;
					continue outer;
				}
			}
			// If all the columns are fully populated, break.
			break outer;
		}
		buff.append(NEW_LINE);
		return buff.toString();
	}

}